class KVue {
  constructor(options) {
    this.$options = options
    this.$data = options.data
    observe(options.data)

    proxy(this)

    new Compiler(options.el, this)
  }
}

function defineReactive(obj, key, val) {
  observe(val)
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    get() {
      console.log('get: ', key, val)
      if (Dep.target) {
        dep.add(Dep.target)
      }
      return val
    },
    set(v) {
      if (v !== val) {
        console.log('set: ', key, v)
        val = v
        dep.notify()
      }
    }
  })
}

function observe(obj) {
  if (typeof obj !== 'object' || obj === null) {
    return
  }

  new Observer(obj)
}

function set(obj, key, val) {
  defineReactive(obj, key, val)
}

function proxy(vm) {
  Object.keys(vm.$data).forEach(key => {
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key]
      },
      set(v) {
        vm.$data[key] = v
      }
    })
  })
}

class Observer {
  constructor(obj) {
    this.value = obj

    this.walk(obj)
  }
  walk(obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key])
    })
  }
}

class Compiler {
  constructor(el, vm) {
    this.$el = document.querySelector(el)
    this.vm = vm

    this.compile(this.$el)
  }

  compile(el) {
    el.childNodes.forEach(node => {
      if (node.nodeType === 1) {
        // 如果有多个子节点，则递归遍历
        if (node.childNodes.length) {
          this.compile(node)
        }
        // 编译元素
        this.compileElement(node)
      } else if (this.isInter(node)) {
        this.compileText(node, RegExp.$1)
      }
    })
  }

  compileElement(node) {
    const attrs = Array.from(node.attributes)
    attrs.forEach(attr => {
      const attrName = attr.name
      const exp = attr.value
      if (this.isDir(attrName)) {
        const dir = attrName.slice(2)
        // this.update(node, exp, dir)
        this[dir] && this[dir](node, exp)
      } else if (this.isEvent(attrName)) {
        const dir = attrName.slice(1)
        this.handleEvent(node, exp, dir)
      }
    })
  }

  isEvent(attrName) {
    return attrName.startsWith('@')
  }

  handleEvent(node, exp, dir) {
    node.addEventListener(dir, (event) => {
      this.vm.$options.methods[exp].call(this.vm, event)
    })
  }

  compileText(node, exp) {
    this.update(node, exp, 'text')
  }

  // 给传入的 node 做初始化，并创建 watcher 并负责其更新
  update(node, exp, dir) {
    // 1.初始化
    const fn = this[dir + 'Updater']
    fn && fn(node, this.vm[exp])

    // 2.创建 watcher
    const watcher = new Watcher(this.vm, exp, (val) => {
      fn && fn(node, val)
    })
    Dep.target = watcher
    this.vm[exp]
    Dep.target = null
  }

  text(node, exp) {
    this.update(node, exp, 'text')
  }

  html(node, exp) {
    this.update(node, exp, 'html')
  }

  model(node, exp) {
    this.update(node, exp, 'model')
    node.addEventListener('input', (event) => {
      this.vm[exp] = event.target.value
    })
  }

  modelUpdater(node, val) {
    console.log(val)
    node.value = val
  }

  textUpdater(node, val) {
    node.textContent = val
  }

  htmlUpdater(node, val) {
    node.innerHTML = val
  }

  isDir(attrName) {
    return attrName.startsWith('k-')
  }

  isInter(node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }
}

class Watcher {
  constructor(vm, key, updateFn) {
    this.vm = vm
    this.key = key
    this.updateFn = updateFn
  }
  update() {
    this.updateFn.call(this.vm, this.vm[this.key])
  }
}

class Dep {
  constructor() {
    this.watchers = []
  }
  notify() {
    this.watchers.forEach(watcher => {
      watcher.update()
    })
  }
  add(watcher) {
    this.watchers.push(watcher)
  }
}